// Copyright (c) Microsoft Corporation.  All rights reserved.
#include "Common.h"

static SCF_ELEMENT_TYPE g_bodyChildElems[]    = { SCF_CONTENT_ELEMENT, SCF_DIALOG_ELEMENT, SCF_MENU_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_clrChildElems[]     = { SCF_EM_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_contentChildElems[] = { SCF_TXT_ELEMENT, SCF_BR_ELEMENT, SCF_IMG_ELEMENT, SCF_BTN_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_dialogChildElems[]  = { SCF_TXT_ELEMENT, SCF_BTN_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_emChildElems[]      = { SCF_CLR_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_itemChildElems[]    = { SCF_BR_ELEMENT, SCF_EM_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_menuChildElems[]    = { SCF_BTN_ELEMENT, SCF_ITEM_ELEMENT, SCF_DIV_ELEMENT, SCF_UNKNOWN_ELEMENT };
static SCF_ELEMENT_TYPE g_txtChildElems[]     = { SCF_EM_ELEMENT, SCF_CLR_ELEMENT, SCF_BR_ELEMENT, SCF_UNKNOWN_ELEMENT };

static SCF_ELEMENT_INFO g_ElemInfo[] = {
    { L"body",    SCF_BODY_ELEMENT,    g_bodyChildElems    },
    { L"br",      SCF_BR_ELEMENT,      NULL                },
    { L"btn",     SCF_BTN_ELEMENT,     NULL                },
    { L"clr",     SCF_CLR_ELEMENT,     g_clrChildElems     },
    { L"content", SCF_CONTENT_ELEMENT, g_contentChildElems },
    { L"dialog",  SCF_DIALOG_ELEMENT,  g_dialogChildElems  },
    { L"div",     SCF_DIV_ELEMENT,     NULL                },
    { L"em",      SCF_EM_ELEMENT,      g_emChildElems      },
    { L"img",     SCF_IMG_ELEMENT,     NULL                },
    { L"item",    SCF_ITEM_ELEMENT,    g_itemChildElems    },
    { L"menu",    SCF_MENU_ELEMENT,    g_menuChildElems    },
    { L"txt",     SCF_TXT_ELEMENT,     g_txtChildElems     },
    { NULL,       SCF_UNKNOWN_ELEMENT, NULL                }
};

//
// ISAXContentHandler
//
HRESULT STDMETHODCALLTYPE CScfParser::putDocumentLocator( 
    /* [in] */ ISAXLocator* pLocator)
{
    UNREFERENCED_PARAMETER(pLocator);
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::startDocument()
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::endDocument()
{
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::startPrefixMapping( 
    /* [in] */ const wchar_t* pwchPrefix,
    /* [in] */ int cchPrefix,
    /* [in] */ const wchar_t* pwchUri,
    /* [in] */ int cchUri)
{
    UNREFERENCED_PARAMETER(pwchPrefix);
    UNREFERENCED_PARAMETER(cchPrefix);
    UNREFERENCED_PARAMETER(pwchUri);
    UNREFERENCED_PARAMETER(cchUri);
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::endPrefixMapping( 
    /* [in] */ const wchar_t* pwchPrefix,
    /* [in] */ int cchPrefix)
{
    UNREFERENCED_PARAMETER(pwchPrefix);
    UNREFERENCED_PARAMETER(cchPrefix);
    
    return S_OK;
}

HRESULT CScfParser::Parse(LPCWSTR pwszContentXml,
    CScfContentBase** ppParsedContent)
{
    HRESULT hr = S_OK;
    VARIANT varXml;

    if (!pwszContentXml)
    {
        return E_POINTER;
    }
    
    VariantInit(&varXml);
    varXml.vt = VT_BSTR;
    varXml.bstrVal = SysAllocString(pwszContentXml);

    if (varXml.bstrVal)
    {
        hr = CScfParser::Parse(&varXml, ppParsedContent);
        
        SysFreeString(varXml.bstrVal);
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    
    return hr;
}

HRESULT CScfParser::Parse(HGLOBAL hGlobal,
    CScfContentBase** ppParsedContent)
{
    HRESULT hr = S_OK;
    VARIANT varXml;

    VariantInit(&varXml);
    varXml.vt = VT_UNKNOWN;
    hr = CreateStreamOnHGlobal(hGlobal, FALSE, (LPSTREAM *)&varXml.punkVal);
    
    if (SUCCEEDED(hr))
    {
        hr = CScfParser::Parse(&varXml, ppParsedContent);
        
        varXml.punkVal->Release();
    }
    
    return hr;
}

HRESULT CScfParser::Parse(VARIANT* pvarXml,
    CScfContentBase** ppParsedContent)
{
    HRESULT hr;
    CComObject<CScfParser>* pSaxHandler = NULL;
    CComPtr<ISAXXMLReader> spSAXXMLReader;

    hr = spSAXXMLReader.CoCreateInstance(CLSID_SAXXMLReader);

    if (SUCCEEDED(hr))
    {
        hr = CComObject<CScfParser>::CreateInstance(&pSaxHandler);
    
        if (SUCCEEDED(hr))
        {
            pSaxHandler->AddRef();
            
            spSAXXMLReader->putContentHandler(pSaxHandler);
            spSAXXMLReader->putErrorHandler((ISAXErrorHandler *)pSaxHandler);
            
            hr = spSAXXMLReader->parse(*pvarXml);
        
            if (SUCCEEDED(hr))
            {
                if (pSaxHandler->m_pContentElement)
                {
                    *ppParsedContent = pSaxHandler->m_pContentElement;
                }
                else
                {
                    // if something is wrong.
                    hr = E_FAIL;
                }
            }
            else
            {
#if DBG
                WCHAR buff[256];
                OutputDebugString(L"*** ParseError ***\r\n");
                StringCchPrintf(buff, COUNTOF(buff), L"hr=%X Line=%d Column=%d hrErrorCode=%X\r\n",
                    hr,
                    pSaxHandler->m_nErrorLine,
                    pSaxHandler->m_nErrorColumn,
                    pSaxHandler->m_hrErrorCode);
                OutputDebugString(buff);
#endif
                
                if (pSaxHandler->m_pContentElement)
                {
                    delete pSaxHandler->m_pContentElement;
                    pSaxHandler->m_pContentElement = NULL;
                }
            }
        }
    }

    if (pSaxHandler)
    {
        pSaxHandler->Release();
    }
    
    return hr;
}

template<class T>
static HRESULT CreateScfElementInstance(ISAXAttributes* pAttributes,
    CScfElement** ppNewElement)
{
    if (!ppNewElement)
    {
        return E_POINTER;
    }
    
    HRESULT hr = S_OK;
    T* pElement = new(std::nothrow) T();
    
    if (pElement)
    {
        hr = pElement->Initialize(pAttributes);

        if (SUCCEEDED(hr))
        {
            *ppNewElement = pElement;
        }
        else
        {
            *ppNewElement = NULL;
            delete pElement;
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    
    return hr;
}

HRESULT STDMETHODCALLTYPE CScfParser::startElement( 
        /* [in] */ const wchar_t* pwchNamespaceUri,
        /* [in] */ int cchNamespaceUri,
        /* [in] */ const wchar_t* pwchLocalName,
        /* [in] */ int cchLocalName,
        /* [in] */ const wchar_t* pwchQName,
        /* [in] */ int cchQName,
        /* [in] */ ISAXAttributes* pAttributes)
{
    
    UNREFERENCED_PARAMETER(cchQName);
    UNREFERENCED_PARAMETER(pwchQName);
    UNREFERENCED_PARAMETER(cchNamespaceUri);
    UNREFERENCED_PARAMETER(pwchNamespaceUri);
    
    SCF_ELEMENT_INFO*   pElemInfo   = NULL;
    CScfElement*        pElem       = NULL;
    HRESULT             hr          = S_OK;

    //
    // Check if this is a supported element
    //
    pElemInfo = _LookupSupportedElement(pwchLocalName, cchLocalName);
    
    if (!pElemInfo)
    {
        // Invalid Element
        hr = E_INVALIDARG;
    }
    
    //
    // Check if this is a valid child element for the current element
    //
    if (SUCCEEDED(hr))
    {
        if (!_IsValidChildElement(m_pCurrentElemInfo, pElemInfo))
        {
            hr = E_INVALIDARG;
        }
    }
    
    if (SUCCEEDED(hr))
    {
        switch (pElemInfo->ElemType)
        {
            case SCF_BODY_ELEMENT:
            {
                /* No Parent Element */
                /* Nothing to do for body element */
                break;
            }
            case SCF_BR_ELEMENT:
            {
                /*
                    Parent Element:
                        content
                        item
                        txt
                */
                if (m_pCurrentElemInfo->ElemType == SCF_CONTENT_ELEMENT)
                {
                    hr = CreateScfElementInstance<CScfBrElement>(pAttributes, &pElem);
                    
                    if (SUCCEEDED(hr))
                    {
                        hr = ((CScfContentElement *)m_pCurrentElem)->AddChild(pElem);
                    }
                }
                else if (m_pCurrentElemInfo->ElemType == SCF_ITEM_ELEMENT)
                {
                    hr = ((CScfItemElement *)m_pCurrentElem)->AppendText(L"\n", 1);
                }
                else if (m_pCurrentElemInfo->ElemType == SCF_TXT_ELEMENT)
                {
                    hr = ((CScfTxtElement *)m_pCurrentElem)->AppendText(L"\n", 1);
                }
                break;
            }
            case SCF_BTN_ELEMENT:
            {
                /*
                    Parent Element:
                        content
                        dialog
                        menu
                */
                
                hr = CreateScfElementInstance<CScfBtnElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    /* append this btn to parent */
                    hr = ((CScfContentBase *)m_pCurrentElem)->AddButton((CScfBtnElement *)pElem);
                }
                
                break;
            }
            case SCF_CLR_ELEMENT:
            {
                /*
                    Parent Element:
                        txt
                */
                if (m_pTxtElement)
                {
                    m_rgbClr = 0;
                    m_nClrStartPos = -1;
                    
                    hr = GetSAXAttributeRgb(pAttributes, &m_rgbClr, TRUE);
                    
                    if (SUCCEEDED(hr))
                    {
                        m_nClrStartPos = m_pTxtElement->GetTextLength();
                    }
                }
                break;
            }
            case SCF_CONTENT_ELEMENT:
            {
                /*
                    Parent Element:
                        body
                */
                if (m_pContentElement)
                {
                    hr = E_INVALIDARG;
                    break;
                }
                
                hr = CreateScfElementInstance<CScfContentElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    m_pContentElement = (CScfContentBase *)pElem;
                }

                break;
            }
            case SCF_DIALOG_ELEMENT:
            {
                /*
                    Parent Element:
                        body
                */
                if (m_pContentElement)
                {
                    hr = E_INVALIDARG;
                    break;
                }

                hr = CreateScfElementInstance<CScfDialogElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    m_pContentElement = (CScfContentBase *)pElem;
                }

                break;
            }
            case SCF_DIV_ELEMENT:
            {
                /*
                    Parent Element:
                        menu
                */
                hr = CreateScfElementInstance<CScfDivElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    if (((CScfMenuElement *)m_pCurrentElem)->GetChildCount() != 0) // div elements should never be the first item on a menu
                    {
                        hr = ((CScfMenuElement *)m_pCurrentElem)->AddChild(pElem);
                    }
                }
                break;
            }
            case SCF_EM_ELEMENT:
            {
                /*
                    Parent Element:
                        txt
                */
                if (m_pTxtElement)
                {
                    m_nEmStartPos = m_pTxtElement->GetTextLength();
                }
                
                break;
            }
            case SCF_IMG_ELEMENT:
            {
                /*
                    Parent Element:
                        content
                */

                hr = CreateScfElementInstance<CScfImgElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    hr = ((CScfContentElement *)m_pCurrentElem)->AddChild(pElem);
                }
                
                break;
            }
            case SCF_ITEM_ELEMENT:
            {
                /*
                    Parent Element:
                        menu
                */
                hr = CreateScfElementInstance<CScfItemElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    if (((CScfItemElement *)pElem)->m_enable)
                    {
                        /* append this item to parent */
                        hr = ((CScfContentBase *)m_pCurrentElem)->AddChild(pElem);
                    }
                    else
                    {
                        // throw away disabled item.
                        delete pElem;
                        pElem = NULL;
                    }
                }
                if (SUCCEEDED(hr))
                {
                    // this is used by <em>
                    m_pItemElement = (CScfItemElement *)pElem;
                }
                
                break;
            }
            case SCF_MENU_ELEMENT:
            {
                /*
                    Parent Element:
                        body
                */
                
                if (m_pContentElement)
                {
                    hr = E_INVALIDARG;
                    break;
                }

                hr = CreateScfElementInstance<CScfMenuElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    m_pContentElement = (CScfContentBase *)pElem;
                }

                break;
            }
            case SCF_TXT_ELEMENT:
            {
                /*
                    Parent Element:
                        content
                        dialog
                */

                hr = CreateScfElementInstance<CScfTxtElement>(pAttributes, &pElem);
                
                if (SUCCEEDED(hr))
                {
                    hr = ((CScfContentElement *)m_pCurrentElem)->AddChild(pElem);
                    
                    if (SUCCEEDED(hr))
                    {
                        // this is used by <clr> and <em>
                        m_pTxtElement = (CScfTxtElement *)pElem;
                    }
                }
                
                break;
            }
            default:
                hr = E_FAIL;
        }
    }
    
    if (SUCCEEDED(hr))
    {
        if (m_pCurrentElemInfo)
        {
            m_ElemStack.push_back(CScfParserElementStackItem(m_pCurrentElemInfo, m_pCurrentElem));
        }
        
        /* pElem can be NULL */
        m_pCurrentElemInfo = pElemInfo;
        m_pCurrentElem = pElem;
    }
    else
    {
        if (pElem)
        {
            delete pElem;
        }
    }
    
    return hr;
}

HRESULT STDMETHODCALLTYPE CScfParser::endElement( 
    /* [in] */ const wchar_t* pwchNamespaceUri,
    /* [in] */ int cchNamespaceUri,
    /* [in] */ const wchar_t* pwchLocalName,
    /* [in] */ int cchLocalName,
    /* [in] */ const wchar_t* pwchQName,
    /* [in] */ int cchQName)
{
    UNREFERENCED_PARAMETER(pwchNamespaceUri);
    UNREFERENCED_PARAMETER(cchNamespaceUri);
    UNREFERENCED_PARAMETER(pwchLocalName);
    UNREFERENCED_PARAMETER(cchLocalName);
    UNREFERENCED_PARAMETER(cchQName);
    UNREFERENCED_PARAMETER(pwchQName);
    
    HRESULT hr = S_OK;
    
    switch (m_pCurrentElemInfo->ElemType)
    {
        case SCF_CLR_ELEMENT:
        {
            if (m_pTxtElement)
            {
                hr = m_pTxtElement->AddClrRange(m_nClrStartPos, m_pTxtElement->GetTextLength(), m_rgbClr);
            }
            break;
        }
        case SCF_EM_ELEMENT:
        {
            if (m_pTxtElement)
            {
                hr = m_pTxtElement->AddEmRange(m_nEmStartPos, m_pTxtElement->GetTextLength());
            }
            break;
        }
        case SCF_TXT_ELEMENT:
        {
            m_pTxtElement = NULL;
            break;
        }
        case SCF_ITEM_ELEMENT:
        {
            m_pItemElement = NULL;
            break;
        }
    }
    
    if (m_ElemStack.empty())
    {
        m_pCurrentElemInfo = NULL;
        m_pCurrentElem = NULL;
    }
    else
    {
        CScfParserElementStackItem item = m_ElemStack.back();
        m_ElemStack.pop_back();
        
        m_pCurrentElemInfo = item.m_pElemInfo;
        m_pCurrentElem = item.m_pElem;
    }
    
    return hr;
}

HRESULT STDMETHODCALLTYPE CScfParser::characters( 
    /* [in] */ const wchar_t* pwchChars,
    /* [in] */ int cchChars)
{
    HRESULT hr = S_OK;

    switch (m_pCurrentElemInfo->ElemType)
    {
        case SCF_ITEM_ELEMENT:
        case SCF_BTN_ELEMENT:
            if (m_pCurrentElem)
            {
                hr = ((CScfSimpleElementBase *)m_pCurrentElem)->AppendText(pwchChars, cchChars);
            }
            break;
        
        case SCF_CLR_ELEMENT:
        case SCF_EM_ELEMENT:
        case SCF_TXT_ELEMENT:
            if (m_pTxtElement)
            {
                const WCHAR *p = pwchChars;
                ULONG c = cchChars;
                
                while(c > 0 && iswspace(*p))
                {
                    p++;
                    c--;
                }
                
                if (c != 0)
                {
//                          m_pTxtElement->AppendText(p, c);
                    m_pTxtElement->AppendText(pwchChars, cchChars);
                }
            }
            else if (m_pItemElement)
            {
                const WCHAR *p = pwchChars;
                ULONG c = cchChars;
                
                while(c > 0 && iswspace(*p))
                {
                    p++;
                    c--;
                }
                
                if (c != 0)
                {
                    m_pItemElement->AppendText(pwchChars, cchChars);
                }
            }
            break;
    }

    return hr;
}

HRESULT STDMETHODCALLTYPE CScfParser::ignorableWhitespace( 
        /* [in] */ const wchar_t* pwchChars,
        /* [in] */ int cchChars)
{
    UNREFERENCED_PARAMETER(pwchChars);
    UNREFERENCED_PARAMETER(cchChars);
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::processingInstruction( 
    /* [in] */ const wchar_t* pwchTarget,
    /* [in] */ int cchTarget,
    /* [in] */ const wchar_t* pwchData,
    /* [in] */ int cchData)
{
    UNREFERENCED_PARAMETER(pwchTarget);
    UNREFERENCED_PARAMETER(cchTarget);
    UNREFERENCED_PARAMETER(pwchData);
    UNREFERENCED_PARAMETER(cchData);
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::skippedEntity( 
    /* [in] */ const wchar_t* pwchName,
    /* [in] */ int cchName)
{
    UNREFERENCED_PARAMETER(pwchName);
    UNREFERENCED_PARAMETER(cchName);
    
    return S_OK;
}

//
// ISAXErrorHandler
//
HRESULT STDMETHODCALLTYPE CScfParser::error(
    /* [in] */ ISAXLocator* pLocator, 
    /* [in] */ const wchar_t* pwchErrorMessage,
    /* [in] */ HRESULT hrErrorCode)
{
    UNREFERENCED_PARAMETER(pwchErrorMessage);
    
    pLocator->getLineNumber(&m_nErrorLine);
    pLocator->getColumnNumber(&m_nErrorColumn);
    m_hrErrorCode = hrErrorCode;
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::fatalError(
    /* [in] */ ISAXLocator* pLocator, 
    /* [in] */ const wchar_t* pwchErrorMessage,
    /* [in] */ HRESULT hrErrorCode)
{
    UNREFERENCED_PARAMETER(pwchErrorMessage);
    
    pLocator->getLineNumber(&m_nErrorLine);
    pLocator->getColumnNumber(&m_nErrorColumn);
    m_hrErrorCode = hrErrorCode;
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE CScfParser::ignorableWarning(
    /* [in] */ ISAXLocator* pLocator, 
    /* [in] */ const wchar_t* pwchErrorMessage,
    /* [in] */ HRESULT hrErrorCode)
{
    UNREFERENCED_PARAMETER(pLocator);
    UNREFERENCED_PARAMETER(pwchErrorMessage);
    UNREFERENCED_PARAMETER(hrErrorCode);
    
    return S_OK;
}

SCF_ELEMENT_INFO* CScfParser::_LookupSupportedElement(
    const wchar_t* pwchName,
    int cchName)
{
    SCF_ELEMENT_INFO* ret = NULL;
    
    for (SCF_ELEMENT_INFO* p = &g_ElemInfo[0]; p->pwszName; p++)
    {
        if (lstrlen(p->pwszName) == cchName &&
            wcsncmp(p->pwszName, pwchName, cchName) == 0)
        {
            ret = p;
            break;
        }
    }
    
    return ret;
}

BOOL CScfParser::_IsValidChildElement(
    SCF_ELEMENT_INFO* pParent,
    SCF_ELEMENT_INFO* pChild)
{
    BOOL bRet = FALSE;
    
    if (pParent == NULL)
    {
        if (pChild->ElemType == SCF_BODY_ELEMENT)
        {
            bRet = TRUE;
        }
    }
    else
    {
        if (pParent->pChildElems)
        {
            for (SCF_ELEMENT_TYPE* pChildElemType = pParent->pChildElems;
                *pChildElemType != SCF_UNKNOWN_ELEMENT;
                pChildElemType++)
            {
                if (*pChildElemType == pChild->ElemType)
                {
                    bRet = TRUE;
                    break;
                }
            }
        }
    }
    
    return bRet;
}

HRESULT GetSAXAttributeUInt(LPCWSTR pwszAttrName,
    ISAXAttributes* pAttributes,
    UINT* puiVal,
    BOOL bRequired,
    int base /* = 10 */)
{
    HRESULT hr = S_OK;
    const wchar_t* pwchValue = NULL;
    int cchValue = 0;
    wchar_t* endprt;
    
    hr = pAttributes->getValueFromQName(pwszAttrName, lstrlen(pwszAttrName), &pwchValue, &cchValue);
    
    if (SUCCEEDED(hr))
    {
        *puiVal = wcstoul(pwchValue, &endprt, base);
    }
    else
    {
        if (!bRequired)
        {
            hr = S_OK;
        }
    }
    
    return hr;
}

HRESULT GetSAXAttributeBool(LPCWSTR pwszAttrName,
    ISAXAttributes* pAttributes,
    BOOL* pBool,
    BOOL bRequired)
{
    HRESULT hr;
    UINT uiTemp = 0;
    
    hr = GetSAXAttributeUInt(pwszAttrName, pAttributes, &uiTemp, TRUE);
    
    if (SUCCEEDED(hr))
    {
        *pBool = uiTemp == 0 ? FALSE : TRUE;
    }
    else
    {
        if (!bRequired)
        {
            hr = S_OK;
        }
    }

    return hr;
}

//
// rgb="RRGGBB"
//
// COLORREF sould be 0xBBGGRR
//
HRESULT GetSAXAttributeRgb(ISAXAttributes* pAttributes,
    COLORREF* pRgb,
    BOOL bRequired)
{
    HRESULT hr;
    UINT uiTemp = 0;
    
    hr = GetSAXAttributeUInt(L"rgb", pAttributes, &uiTemp, TRUE, 16);
    
    if (SUCCEEDED(hr))
    {
        *pRgb = RGB((uiTemp & 0xFF0000) >> 16, (uiTemp & 0xFF00) >> 8, (uiTemp & 0xFF));
    }
    else
    {
        if (!bRequired)
        {
            hr = S_OK;
        }
    }

    return hr;
}

HRESULT GetSAXAttributeString(LPCWSTR pwszAttrName,
    ISAXAttributes* pAttributes,
    wstring& wstr,
    BOOL bRequired)
{
    HRESULT hr = S_OK;
    const wchar_t *pwchValue = NULL;
    int cchValue = 0;
    
    hr = pAttributes->getValueFromQName(pwszAttrName, lstrlen(pwszAttrName), &pwchValue, &cchValue);
    
    if (SUCCEEDED(hr))
    {
        try
        {
            wstr.append(pwchValue, cchValue);
        }
        catch(...)
        {
            hr = E_FAIL;
        }
    }
    else
    {
        if (!bRequired)
        {
            hr = S_OK;
        }
    }
    
    return hr;
}

